<style>
    .expired.certdate{
        font-weight: bolder;
        color: #bd001c;
    }

    .valid.certdate{
        color: #31c071;
    }

    #certifiedDomainList .ui.basic.button{
        margin-top: 0.1em;
    }
</style>
<script src="script/jsrsasign-all-min.js"></script>
<div class="standardContainer">
    <div class="ui basic segment">
        <h2>TLS / SSL Certificates</h2>
        <p>Setup TLS cert for different domains of your reverse proxy server names</p>
    </div>
    
    <div class="ui divider"></div>
    <h3>Hosts Certificates</h3>
    <p>Provide certificates for multiple domains reverse proxy</p>
    <div class="ui fluid form">
        <div class="three fields">
            <div class="field">
            <label>Server Name (Domain)</label>
            <input type="text" id="certdomain" placeholder="">
            <small><i class="exclamation circle yellow icon"></i> Match the server name with your CN/DNS entry in certificate for faster resolve time</small>
            </div>
            <div class="field">
                <label>Public Key (.pem)</label>
                <input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
                <small>or .crt files in order systems</small>
            </div>
            <div class="field">
                <label>Private Key (.key)</label>
                <input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
            </div>
        </div>
        <button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button><br>
        <small>You have intermediate certificate? <a style="cursor:pointer;" onclick="showSideWrapper('snippet/intermediateCertConv.html');">Open Conversion Tool</a></small>
    </div>
    <div id="certUploadSuccMsg" class="ui green message" style="display:none;">
        <i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
    </div>
    <div class="ui message">
        <h4>Tips about Server Names & SNI</h4>
        <div class="ui bulleted list">
            <div class="item">
                If you have two subdomains like <code>a.example.com</code> and <code>b.example.com</code> ,
                for faster response speed, you might want to setup them one by one (i.e. having two seperate certificate for 
                <code>a.example.com</code> and <code>b.example.com</code>).
            </div>
            <div class="item">
                If you have a wildcard certificate that covers <code>*.example.com</code>, 
                you can just enter <code>example.com</code> as server name to add a certificate.
            </div>
            <div class="item">
                If you have a certificate contain multiple host, you can enter the first domain in your certificate 
                and Zoraxy will try to match the remaining CN/DNS for you.
            </div>
        </div>
    </div>
    <p>Current list of loaded certificates</p>
    <div tourstep="certTable" style="width: 100%; overflow-x: auto; padding-bottom: 1em;">
        <div style="min-width: 960px; max-width: 100%;  ">
            <table class="ui unstackable basic celled table">
                <thead>
                <tr>
                    <th>Domain</th>
                    <th>Filename</th>
                    <th>Last Update</th>
                    <th>Expire At</th>
                    <th>Fallback</th>
                    <th>DNS Challenge</th>
                    <th class="no-sort" tourstep="defaultCertificate">Actions</th>
                </tr></thead>
            <tbody id="certifiedDomainList">
    
            </tbody>
            </table>
        </div>
        <br>
        <button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
        <br>
        <div class="ui blue message">
            <h4>What is a Fallback Certificate?</h4>
            <p>A fallback certificate is used when the reverse proxy cannot find a matching certificate for the requested server name. If you are using CloudFlare full strict mode, you can also upload the CloudFlare certificate as fallback certificate.</p>
        </div>
    </div>
    <!-- 
    <div class="ui divider"></div>
    <div tourstep="defaultCertificate">
        <h3>Fallback Certificate</h3>
        <p>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</p>
        <table class="ui very basic unstackable celled table">
            <thead>
                <tr><th class="no-sort">Key Type</th>
                <th  class="no-sort">Found</th>
            </tr></thead>
            <tbody>
                <tr>
                    <td><i class="globe icon"></i> Fallback Public Key</td>
                    <td id="pubkeyExists"></td>
                </tr>
                <tr>
                    <td><i class="lock icon"></i> Fallback Private Key</td>
                    <td id="prikeyExists"></td>
                </tr>
            </tbody>
        </table>
        <p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
        <div class="ui buttons">
            <button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
            <button class="ui basic button" onclick="uploadPrivateKey();"><i class="grey lock icon"></i> Private Key</button>
        </div>
         <div class="ui yellow message"><i class="exclamation triangle icon"></i> We will be removing the fallback certificate section soon. <br>
            Please use "<i class="ui blue home icon"></i>Set Fallback" button in the certificate list above to set the fallback certificate.</div>
    </div>
    -->
    <div class="ui divider"></div>
    <div tourstep="acmeSettings">
        <h3>Certificate Authority (CA) and Auto Renew (ACME)</h3>
        <p>Management features regarding CA and ACME</p>
        <h4>Prefered Certificate Authority</h4>
        <p>The default CA to use when create a new subdomain proxy endpoint with TLS certificate</p>
        <div class="ui fluid form">
            <div class="field">
                <label>Preferred CA</label>
                <div class="ui selection dropdown" id="defaultCA">
                    <input type="hidden" name="defaultCA">
                    <i class="dropdown icon"></i>
                    <div class="default text">Let's Encrypt</div>
                    <div class="menu">
                    <div class="item" data-value="Let's Encrypt">Let's Encrypt</div>
                    <!-- <div class="item" data-value="Buypass">Buypass</div> -->
                    <div class="item" data-value="ZeroSSL">ZeroSSL</div>
                    </div>
                </div>
            </div>
            <div class="field">
                <label>ACME Email</label>
                <input id="prefACMEEmail" type="text" placeholder="ACME Email">
            </div>
            <button class="ui basic icon button" onclick="saveDefaultCA();"><i class="ui blue save icon"></i> Save Settings</button>
        </div><br>
        <h5>Certificate Renew / Generation (ACME) Settings</h5>
        <div class="ui basic segment acmeRenewStateWrapper">
            <h4 class="ui header" id="acmeAutoRenewer">
                <i class="white remove icon"></i>
                <div class="content">
                    <span id="acmeAutoRenewerStatus">Disabled</span>
                    <div class="sub header">ACME Auto-Renewer</div>
                </div>
            </h4>
        </div>
        <p>This tool provide you a graphical interface to setup auto certificate renew on your (sub)domains. You can also manually generate a certificate if one of your domain do not have certificate.</p>
        <button class="ui basic button" tourstep="openACMEManager" onclick="openACMEManager();"><i class="yellow external icon"></i> Open ACME Tool</button>
    </div>
</div>
<script>
    var uploadPendingPublicKey = undefined;
    var uploadPendingPrivateKey = undefined;

    $("#defaultCA").dropdown();

    function getPossibleCommonNameFromSelectedCertificate(){
        const fileInput = document.getElementById('pubkeySelector');
        const file = fileInput.files[0];
        if (!file) {
            msgbox("No certificate file selected", false, 4000);
            return;
        }
        const reader = new FileReader();
        reader.onload = function(e) {
            const certContent = e.target.result;
            $.cjax({
                url: '/api/cert/getCommonName',
                method: 'POST',
                data: { cert: certContent },
                success: function(data) {
                    if (data.error !== undefined) {
                        //Ignore error
                        $("#certdomain").attr("placeholder", "");
                    } else if (data) {
                        if (typeof data === "string" && data.startsWith("*.")) {
                            data = data.substring(2);
                        }
                        $("#certdomain").attr("placeholder", data);
                    }
                },
                error: function(xhr) {
                    //Ignore error
                }
            });
        };
        reader.readAsText(file);
    }

    function setSelectedCertAsFallbackCertificate(certDomain){
        $.cjax({
            url: '/api/cert/setDefault',
            method: 'POST',
            data: { certname: certDomain },
            success: function(data) {
                if (data.error !== undefined) {
                    msgbox(data.error, false, 5000);
                } else {
                    msgbox('Fallback certificate set successfully!');
                    initManagedDomainCertificateList();
                    initDefaultKeypairCheck();
                }
            },
            error: function(xhr) {
                msgbox('Failed to set fallback certificate', false, 5000);
            }
        });
    }

    //Renew certificate by button press
    function renewCertificate(domain, dns, btn=undefined){
        let defaultCA = $("#defaultCA").dropdown("get value");
        if (defaultCA.trim() == ""){
            defaultCA = "Let's Encrypt";
        }
        //Get a new cert using ACME
        msgbox("Requesting certificate via " + defaultCA  +"...");

        //Request ACME for certificate
        let buttonOriginalHTML = "";
        if (btn != undefined){
            buttonOriginalHTML = $(btn).html();
            $(btn).addClass('disabled');
            $(btn).html(`<i class="ui loading spinner icon"></i>`);
        }

        obtainCertificate(domain, dns, defaultCA.trim(), function(succ){
            if (btn != undefined){
                $(btn).removeClass('disabled');
                if ($(btn).hasClass("icon")){
                    //Only change the button icon
                    if (succ){
                        $(btn).html(`<i class="ui green check icon"></i>`);
                    }else{
                        $(btn).html(`<i class="ui red times icon"></i>`);
                    }
                }else{
                    //Show error or success icon with text
                    if (succ){
                        $(btn).html(`<i class="ui green check icon"></i> Requested`);
                    }else{
                        $(btn).html(`<i class="ui red times icon"></i> Error`);
                    }
                }

                //Restore the button after 3 seconds
                setTimeout(function(){
                    $(btn).html(buttonOriginalHTML);
                }, 3000);
                
                setTimeout(function(){
                    initManagedDomainCertificateList();
                }, 3000);
            }
        });
    }

    /*
        Obtain Certificate via ACME
    */

   // Obtain certificate from API, only support one domain
   function obtainCertificate(domains, dns, usingCa = "Let's Encrypt", callback=undefined) {
        //Load the ACME email from server side
        let acmeEmail = "";
        $.get("/api/acme/autoRenew/email", function(data){
            if (data != "" && data != undefined && data != null){
                acmeEmail = data;
            }

            let filename = "";
            let email = acmeEmail;
            if (acmeEmail == ""){
                msgbox("Unable to obtain certificate: ACME email not set", false, 8000);
                if (callback != undefined){
                    callback(false);
                }
                return;
            }
            if (filename.trim() == "" && !domains.includes(",")){
                //Zoraxy filename are the matching name for domains.
                //Use the same as domains
                filename = domains;
            }else if (filename != "" && !domains.includes(",")){
                //Invalid settings. Force the filename to be same as domain
                //if there are only 1 domain
                filename = domains;
            }else{
                msgbox("Filename cannot be empty for certs containing multiple domains.")
                if (callback != undefined){
                    callback(false);
                }
                return;
            }
            //Filename cannot contain wildcards, and wildcards are possible with DNS challenges
            filename = filename.replace("*", "_");

            $.ajax({
                url: "/api/acme/obtainCert",
                method: "GET",
                data: {
                    domains: domains,
                    filename: filename,
                    email: email,
                    ca: usingCa,
                    dns: dns
                },
                success: function(response) {
                    if (response.error) {
                        console.log("Error:", response.error);
                        // Show error message
                        msgbox(response.error, false, 12000);
                        if (callback != undefined){
                            callback(false);
                        }
                    } else {
                        console.log("Certificate installed successfully");
                        // Show success message
                        msgbox("Certificate installed successfully");
                        
                        if (callback != undefined){
                            callback(true);
                        }
                    }
                },
                error: function(error) {
                    console.log("Failed to install certificate:", error);
                }
            });
        });
    }


    //Delete the certificate by its domain
    function deleteCertificate(domain){
        if (confirm("Confirm delete certificate for " + domain + " ?")){
            $.cjax({
                url: "/api/cert/delete",
                method: "POST",
                data: {domain: domain},
                success: function(data){
                    if (data.error != undefined){
                        msgbox(data.error, false, 5000);
                    }else{
                        initManagedDomainCertificateList();
                        initDefaultKeypairCheck();
                    }
                }
            });
        }
        
    }

    function initAcmeStatus(){
        //Initialize the current default CA options
        $.get("/api/acme/autoRenew/email", function(data){
            $("#prefACMEEmail").val(data);
            if (data.trim() == ""){
                //acme email is not yet set
                $(".renewButton").addClass('disabled');
            }else{
                $(".renewButton").removeClass('disabled');
            }
        });

        $.get("/api/acme/autoRenew/ca", function(data){
            $("#defaultCA").dropdown("set value", data);
        });

        $.get("/api/acme/autoRenew/enable", function(data){
            setACMEEnableStates(data);
        })
    }
    //Set the status of the acme enable icon
    function setACMEEnableStates(enabled){
        $("#acmeAutoRenewerStatus").text(enabled?"Enabled":"Disabled");
        if (enabled){
            $(".acmeRenewStateWrapper").addClass("enabled");
        }else{
            $(".acmeRenewStateWrapper").removeClass("enabled");
        }
        
        $("#acmeAutoRenewer").find("i").attr("class", enabled?"white circle check icon":"white circle times icon");
    }
    initAcmeStatus();

    function saveDefaultCA(){
        let newDefaultEmail = $("#prefACMEEmail").val().trim();
        let newDefaultCA = $("#defaultCA").dropdown("get value");

        if (newDefaultEmail == ""){
            msgbox("Invalid acme email given", false);
            return;
        }

        $.cjax({
            url: "/api/acme/autoRenew/email",
            method: "POST",
            data: {"set": newDefaultEmail},
            success: function(data){
                if (data.error != undefined){
                    msgbox(data.error, false);
                }else{
                    //Update the renew button states
                    $(".renewButton").removeClass('disabled');
                }
            }
        });

        $.cjax({
            url: "/api/acme/autoRenew/ca",
            data: {"set": newDefaultCA},
            method: "POST",
            success: function(data){
                if (data.error != undefined){
                    msgbox(data.error, false);
                }
            }
        });

        msgbox("Settings updated");

    }

    //List the stored certificates
    function initManagedDomainCertificateList(){
        $.get("/api/cert/list?date=true", function(data){
            if (data.error != undefined){
                msgbox(data.error, false, 5000);
            }else{
                $("#certifiedDomainList").html("");
                data.sort((a,b) => {
                    return a.Domain > b.Domain
                });
                data.forEach(entry => {
                    let isExpired = entry.RemainingDays <= 0;
                    let entryDomainRenewKey = entry.Filename; 
                    if (entryDomainRenewKey.includes("_.")){
                        entryDomainRenewKey = entryDomainRenewKey.replace("_.","*.");
                    }
                    $("#certifiedDomainList").append(`<tr>
                        <td><a style="cursor: pointer;" title="Download certificate" onclick="handleCertDownload('${entry.Filename}');">${entry.Domain}</a></td>
                        <td>${entry.Filename}</td>
                        <td>${entry.LastModifiedDate}</td>
                        <td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
                        <td>${entry.IsFallback?"<i class='green check icon'></i>":""}</td>
                        <td><i class="${entry.UseDNS?"green check": "red times"} icon"></i></td>
                        <td>
                            <button title="Set as Default / Fallback Certificate" class="ui mini basic button ${(entry.IsFallback?"disabled":"")} " onclick="setSelectedCertAsFallbackCertificate('${entry.Filename}');"><i class="ui blue home icon"></i> Set Fallback</button>
                            <button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entryDomainRenewKey}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button>
                            <button title="Delete key-pair" class="ui mini basic icon red button" onclick="deleteCertificate('${entry.Filename}');"><i class="ui red trash icon"></i></button>
                        </td>
                    </tr>`);
                });

                if (data.length == 0){
                    $("#certifiedDomainList").append(`<tr>
                        <td colspan="4"><i class="ui times red circle icon"></i> No valid keypairs found</td>
                    </tr>`);
                }
            }
        })
    }
    initManagedDomainCertificateList();

    function openACMEManager(){
        showSideWrapper('snippet/acme.html');
    }

    function handleDomainUploadByKeypress(){
        handleDomainKeysUpload(function(){
            $("#certUploadingDomain").text($("#certdomain").val().trim());
            //After uploaded, reset the file selector
            document.getElementById('pubkeySelector').value = '';
            document.getElementById('prikeySelector').value = '';
            document.getElementById('certdomain').value = '';
            $("#certdomain").attr("placeholder", "");
            uploadPendingPublicKey = undefined;
            uploadPendingPrivateKey = undefined;

            //Show succ
            $("#certUploadSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
            initManagedDomainCertificateList();
        });
    }

    function handleCertDownload(certName){
        $.get("/api/cert/download?seek=true&certname=" + certName, function(data){
            if (data.error != undefined){
                //Error resolving certificate
                msgbox(data.error, false);
            }else{
                //Continue to download  
                window.open("/api/cert/download?certname=" + certName);
            }
        });
    }

    //Handle domain keys upload
    function handleDomainKeysUpload(callback=undefined){
        let domain = $("#certdomain").val();
        if (domain.trim() == ""){
            //Check if placeholder has value
            if ($("#certdomain").attr("placeholder").trim() != ""){
                domain = $("#certdomain").attr("placeholder").trim();
            }else{
                domain = undefined;
            }

            if (domain == undefined || domain.trim() == "") {
                msgbox("Missing domain", false, 5000);
                return;
            }
        }
        if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') {
            const publicKeyForm = new FormData();
            const csrfToken = document.querySelector('meta[name="zoraxy.csrf.Token"]').getAttribute("content");

            publicKeyForm.append('file', uploadPendingPublicKey, 'publicKey');

            const privateKeyForm = new FormData();
            privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey');

            const publicKeyRequest = new XMLHttpRequest();
            publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain);
            publicKeyRequest.setRequestHeader('X-CSRF-Token', csrfToken);
            publicKeyRequest.onreadystatechange = function() {
            if (publicKeyRequest.readyState === XMLHttpRequest.DONE) {
                if (publicKeyRequest.status !== 200) {
                    msgbox('Error uploading public key: ' + publicKeyRequest.statusText, false, 5000);
                }

                if (callback != undefined){
                    callback();
                }
                
            }
            };
            publicKeyRequest.send(publicKeyForm);

            const privateKeyRequest = new XMLHttpRequest();
            privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain);
            privateKeyRequest.setRequestHeader('X-CSRF-Token', csrfToken);
            privateKeyRequest.onreadystatechange = function() {
            if (privateKeyRequest.readyState === XMLHttpRequest.DONE) {
                if (privateKeyRequest.status !== 200) {
                    msgbox('Error uploading private key: ' + privateKeyRequest.statusText, false, 5000);
                }
                if (callback != undefined){
                    callback();
                }
            }
            };
            privateKeyRequest.send(privateKeyForm);
        } else {
            msgbox('One or both of the files is missing or not a file object');
        }
    }

    //Handlers for selecting domain based key pairs
    //ktype = {"pub" / "pri"}
    function handleFileSelect(event, ktype="pub") {
        const file = event.target.files[0];
        if (ktype == "pub"){
            uploadPendingPublicKey = file;
            getPossibleCommonNameFromSelectedCertificate();
        }else if (ktype == "pri"){
            uploadPendingPrivateKey = file;
        }
    }

    //Check if the default keypairs exists
    function initDefaultKeypairCheck(){
        $.get("/api/cert/checkDefault", function(data){
            let tick = `<i class="ui green checkmark icon"></i>`;
            let cross = `<i class="ui red times icon"></i>`;
            $("#pubkeyExists").html(data.DefaultPubExists?tick:cross);
            $("#prikeyExists").html(data.DefaultPriExists?tick:cross);
        });
    }
    initDefaultKeypairCheck();

    function uploadPrivateKey(){
        // create file input element
        const input = document.createElement('input');
        input.type = 'file';
        
        // add change listener to file input
        input.addEventListener('change', () => {
            // create form data object
            const formData = new FormData();
            const csrfToken = document.querySelector('meta[name="zoraxy.csrf.Token"]').getAttribute("content");

            // add selected file to form data
            formData.append('file', input.files[0]);

            // send form data to server
            fetch('/api/cert/upload?ktype=pri', {
                method: 'POST',
                body: formData,
                headers: {
                    'X-CSRF-Token': csrfToken
                }
            })
            .then(response => {
                initDefaultKeypairCheck();
                if (response.ok) {
                    msgbox('File upload successful!');
                } else {
                    response.text().then(text => {
                        msgbox(text, false, 5000);
                    });
                    //console.log(response.text());
                    //alert('File upload failed!');
                }
                })
            .catch(error => {
                msgbox('An error occurred while uploading the file.', false, 5000);
                console.error(error);
            });
        });
        
        // click file input to open file selector
        input.click();
    }

    function uploadPublicKey() {
        // create file input element
        const input = document.createElement('input');
        const csrfToken = document.querySelector('meta[name="zoraxy.csrf.Token"]').getAttribute("content");
        input.type = 'file';
        
        // add change listener to file input
        input.addEventListener('change', () => {
            // create form data object
            const formData = new FormData();
            
            // add selected file to form data
            formData.append('file', input.files[0]);

            // send form data to server
            fetch('/api/cert/upload?ktype=pub', {
                method: 'POST',
                body: formData,
                headers: {
                    'X-CSRF-Token': csrfToken
                }
            })
            .then(response => {
                if (response.ok) {
                    msgbox('File upload successful!');
                    initDefaultKeypairCheck();
                } else {
                    response.text().then(text => {
                        msgbox(text, false, 5000);
                    });
                    //console.log(response.text());
                    //alert('File upload failed!');
                }
                })
            .catch(error => {
                msgbox('An error occurred while uploading the file.', false, 5000);
                console.error(error);
            });
        });
        
        // click file input to open file selector
        input.click();
    }
</script>
